package main

import (
	"errors"
	"fmt"
	"log"
	"sync"
	"time"
)

const (
	timesIDBits   = 41 // 时间戳(ms)
	machineIdBits = 10 // 机器id
	sequenceBits  = 12 // 同一毫秒内的序列号id

	// 最大值计算
	timesIDMaxValue   = -1 ^ (-1 << timesIDBits)
	machineIdMaxValue = -1 ^ (-1 << machineIdBits) // 机器 ID 最大值
	sequenceMask      = -1 ^ (-1 << sequenceBits)  // 序列号掩码

	// 位移
	timestampShift = machineIdBits + sequenceBits
	machineShift   = sequenceBits
)

// Snowflake 一个雪花结构体
type Snowflake struct {
	mu        *sync.Mutex // 互斥锁
	StartTime time.Time   // 启动时间
	Timestamp int64       // 时间戳
	machineId int64       // 机器号
	sequence  int64       // 序列号
}

// NewSnowflake 创建的雪花对象，要输入机器id
func NewSnowflake(StartTime time.Time, machineID int64) (*Snowflake, error) {
	// 判断传入的初始化时间是否合理
	if StartTime.After(time.Now()) {
		return nil, errors.New("start time is ahead of now")
	}

	// 判断传入的机器id是否合理
	if machineID < 0 || machineID > machineIdMaxValue {
		return nil, errors.New("machineID out of range")
	}

	// 计算时间戳
	Epoch := time.Since(StartTime).Milliseconds()

	return &Snowflake{
		mu:        new(sync.Mutex),
		StartTime: StartTime,
		Timestamp: Epoch, // 初始化时将时间戳设为 启动项目时的时间戳
		machineId: machineID,
		sequence:  0,
	}, nil
}

// NextID 生成唯一 ID
func (s *Snowflake) NextID() (int64, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	// 如果序列号已经达到最大值，需要进位到下一个时间戳
	if s.sequence == sequenceMask {
		s.sequence = 0 // 重置序列号
		s.Timestamp++  // 时间戳加 1
	} else {
		s.sequence++ // 序列号加 1
	}

	// 如果时间戳超出最大值，表示生成器溢出
	if s.Timestamp > timesIDMaxValue {
		return 0, errors.New("timestamp overflow")
	}

	// 计算唯一 ID
	timePart := s.Timestamp << timestampShift
	machinePart := s.machineId << machineShift
	sequencePart := s.sequence

	snowID := timePart | machinePart | sequencePart
	return snowID, nil
}

func main() {
	// 初始化 Snowflake
	startTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
	sf, err := NewSnowflake(startTime, 1)
	if err != nil {
		panic(err)
	}

	// 生成 ID
	for i := 0; i < 10; i++ {
		id, err := sf.NextID()
		if err != nil {
			log.Printf("生成 ID 时出错: %v", err)
			continue
		}
		fmt.Println(id)
	}

	fmt.Println("ok")
}
